Sveobuhvatan vodič za optimizaciju sakupljanja smeća (GC) u WebAssemblyju, s fokusom na strategije, tehnike i najbolje prakse za postizanje vrhunskih performansi.
Podešavanje performansi WebAssembly GC-a: Ovladavanje optimizacijom sakupljanja smeća
WebAssembly (WASM) je revolucionarizirao web razvoj omogućujući performanse bliske nativnima u pregledniku. S uvođenjem podrške za sakupljanje smeća (Garbage Collection - GC), WASM postaje još moćniji, pojednostavljujući razvoj složenih aplikacija i omogućujući prijenos postojećih kodnih baza. Međutim, kao i svaka tehnologija koja se oslanja na GC, postizanje optimalnih performansi zahtijeva duboko razumijevanje načina rada GC-a i kako ga učinkovito podesiti. Ovaj članak pruža sveobuhvatan vodič za podešavanje performansi WebAssembly GC-a, pokrivajući strategije, tehnike i najbolje prakse primjenjive na različitim platformama i preglednicima.
Razumijevanje WebAssembly GC-a
Prije nego što zaronimo u tehnike optimizacije, ključno je razumjeti osnove WebAssembly GC-a. Za razliku od jezika poput C-a ili C++-a, koji zahtijevaju ručno upravljanje memorijom, jezici koji ciljaju WASM s GC-om, kao što su JavaScript, C#, Kotlin i drugi putem okvira, mogu se osloniti na runtime za automatsko upravljanje alokacijom i dealokacijom memorije. To pojednostavljuje razvoj i smanjuje rizik od curenja memorije i drugih grešaka povezanih s memorijom. Međutim, automatska priroda GC-a ima svoju cijenu: GC ciklus može uvesti pauze i utjecati na performanse aplikacije ako se ne upravlja ispravno.
Ključni pojmovi
- Gomila (Heap): Memorijsko područje gdje se objekti alociraju. U WebAssembly GC-u, ovo je upravljana gomila, različita od linearne memorije koja se koristi za druge WASM podatke.
- Sakupljač smeća (Garbage Collector): Komponenta runtimea odgovorna za identificiranje i oslobađanje neiskorištene memorije. Postoje različiti GC algoritmi, svaki sa svojim karakteristikama performansi.
- GC ciklus: Proces identificiranja i oslobađanja neiskorištene memorije. To obično uključuje označavanje živih objekata (objekata koji se još uvijek koriste) i zatim čišćenje ostatka.
- Vrijeme pauze: Trajanje tijekom kojeg je aplikacija pauzirana dok se GC ciklus izvodi. Smanjenje vremena pauze ključno je za postizanje glatkih i responzivnih performansi.
- Propusnost (Throughput): Postotak vremena koje aplikacija provodi izvršavajući kod u odnosu na vrijeme provedeno u GC-u. Maksimiziranje propusnosti je još jedan ključni cilj optimizacije GC-a.
- Memorijski otisak: Količina memorije koju aplikacija troši. Učinkovit GC može pomoći u smanjenju memorijskog otiska i poboljšanju ukupnih performansi sustava.
Identificiranje uskih grla u performansama GC-a
Prvi korak u optimizaciji performansi WebAssembly GC-a je identificiranje potencijalnih uskih grla. To zahtijeva pažljivo profiliranje i analizu upotrebe memorije i ponašanja GC-a vaše aplikacije. Nekoliko alata i tehnika može pomoći:
Alati za razvojne programere u pregledniku
Moderni preglednici pružaju izvrsne alate za razvojne programere koji se mogu koristiti za praćenje aktivnosti GC-a. Kartica "Performance" u Chromeu, Firefoxu i Edgeu omogućuje vam snimanje vremenske linije izvršavanja vaše aplikacije i vizualizaciju GC ciklusa. Potražite duge pauze, česte GC cikluse ili prekomjernu alokaciju memorije.
Primjer: U Chrome DevTools, koristite karticu "Performance". Snimite sesiju rada vaše aplikacije. Analizirajte grafikon "Memory" kako biste vidjeli veličinu gomile i GC događaje. Dugi skokovi u "JS Heap" ukazuju na potencijalne probleme s GC-om. Također možete koristiti odjeljak "Garbage Collection" pod "Timings" kako biste ispitali trajanje pojedinih GC ciklusa.
WASM profileri
Specijalizirani WASM profileri mogu pružiti detaljnije uvide u alokaciju memorije i ponašanje GC-a unutar samog WASM modula. Ovi alati mogu pomoći u preciznom određivanju specifičnih funkcija ili dijelova koda koji su odgovorni za prekomjernu alokaciju memorije ili pritisak na GC.
Zapisivanje (logging) i metrike
Dodavanje prilagođenog zapisivanja i metrika u vašu aplikaciju može pružiti vrijedne podatke o upotrebi memorije, stopama alokacije objekata i vremenima GC ciklusa. To može biti posebno korisno za identificiranje uzoraka ili trendova koji možda nisu vidljivi samo iz alata za profiliranje.
Primjer: Instrumentirajte svoj kod za zapisivanje veličine alociranih objekata. Pratite broj alokacija u sekundi za različite tipove objekata. Koristite alat za praćenje performansi ili sustav izrađen po mjeri za vizualizaciju ovih podataka tijekom vremena. To će pomoći u otkrivanju curenja memorije ili neočekivanih uzoraka alokacije.
Strategije za optimizaciju performansi WebAssembly GC-a
Nakon što ste identificirali potencijalna uska grla u performansama GC-a, možete primijeniti različite strategije za poboljšanje performansi. Ove strategije se mogu općenito podijeliti u sljedeća područja:
1. Smanjite alokaciju memorije
Najučinkovitiji način za poboljšanje performansi GC-a je smanjenje količine memorije koju vaša aplikacija alocira. Manje alokacije znači manje posla za GC, što rezultira kraćim vremenima pauze i većom propusnošću.
- Grupiranje objekata (Object Pooling): Ponovno koristite postojeće objekte umjesto stvaranja novih. To može biti posebno učinkovito za često korištene objekte poput vektora, matrica ili privremenih struktura podataka.
- Predmemoriranje objekata (Object Caching): Pohranite često pristupane objekte u predmemoriju (cache) kako biste izbjegli njihovo ponovno izračunavanje ili dohvaćanje. To može smanjiti potrebu za alokacijom memorije i poboljšati ukupne performanse.
- Optimizacija struktura podataka: Odaberite strukture podataka koje su učinkovite u pogledu upotrebe memorije i alokacije. Na primjer, korištenje niza fiksne veličine umjesto dinamički rastuće liste može smanjiti alokaciju memorije i fragmentaciju.
- Nepromjenjive (immutable) strukture podataka: Korištenje nepromjenjivih struktura podataka može smanjiti potrebu za kopiranjem i modificiranjem objekata, što može dovesti do manje alokacije memorije i boljih performansi GC-a. Knjižnice poput Immutable.js (iako dizajnirane za JavaScript, principi se primjenjuju) mogu se prilagoditi ili poslužiti kao inspiracija za stvaranje nepromjenjivih struktura podataka u drugim jezicima koji se kompajliraju u WASM s GC-om.
- Alokatori arena (Arena Allocators): Alocirajte memoriju u velikim komadima (arenama) i zatim alocirajte objekte unutar tih arena. To može smanjiti fragmentaciju i poboljšati brzinu alokacije. Kada arena više nije potrebna, cijeli komad se može osloboditi odjednom, izbjegavajući potrebu za oslobađanjem pojedinačnih objekata.
Primjer: U game engineu, umjesto stvaranja novog Vector3 objekta svakog okvira za svaku česticu, koristite grupiranje objekata (object pool) za ponovnu upotrebu postojećih Vector3 objekata. To značajno smanjuje broj alokacija i poboljšava performanse GC-a. Možete implementirati jednostavno grupiranje objekata održavanjem liste dostupnih Vector3 objekata i pružanjem metoda za dohvaćanje i oslobađanje objekata iz grupe.
2. Minimizirajte životni vijek objekata
Što duže objekt živi, veća je vjerojatnost da će ga GC obraditi. Minimiziranjem životnog vijeka objekata možete smanjiti količinu posla koju GC mora obaviti.
- Odgovarajuće definirajte doseg varijabli: Deklarirajte varijable u najmanjem mogućem dosegu (scope). To im omogućuje da budu sakupljene kao smeće brže nakon što više nisu potrebne.
- Pravovremeno oslobađajte resurse: Ako objekt drži resurse (npr. datotečne ručice, mrežne veze), oslobodite te resurse čim više nisu potrebni. To može osloboditi memoriju i smanjiti vjerojatnost da će objekt biti obrađen od strane GC-a.
- Izbjegavajte globalne varijable: Globalne varijable imaju dug životni vijek i mogu doprinijeti pritisku na GC. Minimizirajte upotrebu globalnih varijabli i razmislite o korištenju ubacivanja ovisnosti (dependency injection) ili drugih tehnika za upravljanje životnim vijekom objekata.
Primjer: Umjesto deklariranja velikog niza na vrhu funkcije, deklarirajte ga unutar petlje gdje se stvarno koristi. Nakon što petlja završi, niz će biti podoban za sakupljanje smeća. To smanjuje životni vijek niza i poboljšava performanse GC-a. U jezicima s blokovskim dosegom (poput JavaScripta s `let` i `const`), osigurajte korištenje tih značajki za ograničavanje dosega varijabli.
3. Optimizirajte strukture podataka
Izbor struktura podataka može imati značajan utjecaj na performanse GC-a. Odaberite strukture podataka koje su učinkovite u pogledu upotrebe memorije i alokacije.
- Koristite primitivne tipove: Primitivni tipovi (npr. cijeli brojevi, booleani, brojevi s pomičnim zarezom) obično su učinkovitiji od objekata. Koristite primitivne tipove kad god je to moguće kako biste smanjili alokaciju memorije i pritisak na GC.
- Minimizirajte overhead objekata: Svaki objekt ima određenu količinu overhead-a povezanu s njim. Minimizirajte overhead objekata korištenjem jednostavnijih struktura podataka ili kombiniranjem više objekata u jedan.
- Razmotrite strukture i vrijednosne tipove: U jezicima koji podržavaju strukture (structs) ili vrijednosne tipove (value types), razmislite o njihovom korištenju umjesto klasa ili referentnih tipova. Strukture se obično alociraju na stogu (stack), što izbjegava overhead GC-a.
- Kompaktan prikaz podataka: Predstavite podatke u kompaktnom formatu kako biste smanjili upotrebu memorije. Na primjer, korištenje bitnih polja za pohranu booleanskih zastavica ili korištenje cjelobrojnog kodiranja za predstavljanje nizova znakova može značajno smanjiti memorijski otisak.
Primjer: Umjesto korištenja niza booleanskih objekata za pohranu skupa zastavica, koristite jedan cijeli broj i manipulirajte pojedinačnim bitovima pomoću bitovnih operatora. To značajno smanjuje upotrebu memorije i pritisak na GC.
4. Minimizirajte prijelaze između jezika
Ako vaša aplikacija uključuje komunikaciju između WebAssemblyja i JavaScripta, minimiziranje učestalosti i količine podataka koji se razmjenjuju preko jezične granice može značajno poboljšati performanse. Prelazak ove granice često uključuje marshalling i kopiranje podataka, što može biti skupo u smislu alokacije memorije i pritiska na GC.
- Grupni prijenos podataka: Umjesto prijenosa podataka element po element, grupirajte prijenose podataka u veće komade. To smanjuje overhead povezan s prelaskom jezične granice.
- Koristite tipizirane nizove (Typed Arrays): Koristite tipizirane nizove (npr. `Uint8Array`, `Float32Array`) za učinkovit prijenos podataka između WebAssemblyja i JavaScripta. Tipizirani nizovi pružaju niskorazinski, memorijski učinkovit način pristupa podacima u oba okruženja.
- Minimizirajte serijalizaciju/deserijalizaciju objekata: Izbjegavajte nepotrebnu serijalizaciju i deserijalizaciju objekata. Ako je moguće, prenesite podatke izravno kao binarne podatke ili koristite zajednički memorijski međuspremnik.
- Koristite dijeljenu memoriju (Shared Memory): WebAssembly i JavaScript mogu dijeliti zajednički memorijski prostor. Koristite dijeljenu memoriju kako biste izbjegli kopiranje podataka prilikom njihovog prijenosa. Međutim, budite svjesni problema s istovremenošću i osigurajte da su uspostavljeni odgovarajući mehanizmi sinkronizacije.
Primjer: Prilikom slanja velikog niza brojeva iz WebAssemblyja u JavaScript, koristite `Float32Array` umjesto pretvaranja svakog broja u JavaScript broj. To izbjegava overhead stvaranja i sakupljanja smeća za mnoge JavaScript brojevne objekte.
5. Razumijte svoj GC algoritam
Različiti WebAssembly runtimeovi (preglednici, Node.js s podrškom za WASM) mogu koristiti različite GC algoritme. Razumijevanje karakteristika specifičnog GC algoritma koji koristi vaš ciljni runtime može vam pomoći da prilagodite svoje strategije optimizacije. Uobičajeni GC algoritmi uključuju:
- Označi i pometi (Mark and Sweep): Osnovni GC algoritam koji označava žive objekte i zatim čisti ostatak. Ovaj algoritam može dovesti do fragmentacije i dugih vremena pauze.
- Označi i sažmi (Mark and Compact): Slično kao označi i pometi, ali također sažima gomilu kako bi se smanjila fragmentacija. Ovaj algoritam može smanjiti fragmentaciju, ali i dalje može imati duga vremena pauze.
- Generacijski GC (Generational GC): Dijeli gomilu na generacije i češće sakuplja mlađe generacije. Ovaj algoritam se temelji na opažanju da većina objekata ima kratak životni vijek. Generacijski GC često pruža bolje performanse od algoritama označi i pometi ili označi i sažmi.
- Inkrementalni GC (Incremental GC): Obavlja GC u malim koracima, ispreplićući GC cikluse s izvršavanjem koda aplikacije. To smanjuje vremena pauze, ali može povećati ukupni overhead GC-a.
- Konkurentni GC (Concurrent GC): Obavlja GC istovremeno s izvršavanjem koda aplikacije. To može značajno smanjiti vremena pauze, ali zahtijeva pažljivu sinkronizaciju kako bi se izbjeglo oštećenje podataka.
Konzultirajte dokumentaciju za vaš ciljni WebAssembly runtime kako biste utvrdili koji se GC algoritam koristi i kako ga konfigurirati. Neki runtimeovi mogu pružati opcije za podešavanje GC parametara, kao što su veličina gomile ili učestalost GC ciklusa.
6. Optimizacije specifične za prevoditelj i jezik
Specifičan prevoditelj (compiler) i jezik koji koristite za ciljanje WebAssemblyja također mogu utjecati na performanse GC-a. Određeni prevoditelji i jezici mogu pružati ugrađene optimizacije ili jezične značajke koje mogu poboljšati upravljanje memorijom i smanjiti pritisak na GC.
- AssemblyScript: AssemblyScript je jezik sličan TypeScriptu koji se izravno kompajlira u WebAssembly. Nudi preciznu kontrolu nad upravljanjem memorijom i podržava alokaciju linearne memorije, što može biti korisno za optimizaciju performansi GC-a. Iako AssemblyScript sada podržava GC putem standardnog prijedloga, razumijevanje kako optimizirati za linearnu memoriju i dalje pomaže.
- TinyGo: TinyGo je Go prevoditelj posebno dizajniran za ugrađene sustave i WebAssembly. Nudi malu veličinu binarne datoteke i učinkovito upravljanje memorijom, što ga čini pogodnim za okruženja s ograničenim resursima. TinyGo podržava GC, ali je također moguće onemogućiti GC i ručno upravljati memorijom.
- Emscripten: Emscripten je lanac alata koji vam omogućuje kompajliranje C i C++ koda u WebAssembly. Pruža različite opcije za upravljanje memorijom, uključujući ručno upravljanje memorijom, emulirani GC i nativnu podršku za GC. Emscriptenova podrška za prilagođene alokatore može biti korisna za optimizaciju obrazaca alokacije memorije.
- Rust (putem WASM kompilacije): Rust se fokusira na sigurnost memorije bez sakupljanja smeća. Njegov sustav vlasništva i posuđivanja sprječava curenje memorije i viseće pokazivače u vrijeme kompilacije. Nudi finu kontrolu nad alokacijom i dealokacijom memorije. Međutim, podrška za WASM GC u Rustu se još uvijek razvija, a interoperabilnost s drugim jezicima temeljenim na GC-u može zahtijevati korištenje mosta ili među-reprezentacije.
Primjer: Kada koristite AssemblyScript, iskoristite njegove mogućnosti upravljanja linearnom memorijom za ručnu alokaciju i dealokaciju memorije za dijelove koda koji su kritični za performanse. To može zaobići GC i pružiti predvidljivije performanse. Pobrinite se da ispravno rukujete svim slučajevima upravljanja memorijom kako biste izbjegli curenje memorije.
7. Dijeljenje koda (Code Splitting) i lijeno učitavanje (Lazy Loading)
Ako je vaša aplikacija velika i složena, razmislite o njenom dijeljenju na manje module i njihovom učitavanju na zahtjev. To može smanjiti početni memorijski otisak i poboljšati vrijeme pokretanja. Odgađanjem učitavanja ne-esencijalnih modula, možete smanjiti količinu memorije kojom GC mora upravljati pri pokretanju.
Primjer: U web aplikaciji, podijelite kod na module odgovorne za različite značajke (npr. renderiranje, korisničko sučelje, logika igre). Učitajte samo module potrebne za početni prikaz, a zatim učitajte druge module kako korisnik interagira s aplikacijom. Ovaj pristup se uobičajeno koristi u modernim web okvirima poput Reacta, Angulara i Vue.js-a te njihovim WASM pandanima.
8. Razmotrite ručno upravljanje memorijom (s oprezom)
Iako je cilj WASM GC-a pojednostaviti upravljanje memorijom, u određenim scenarijima kritičnim za performanse, vraćanje na ručno upravljanje memorijom može biti nužno. Ovaj pristup pruža najveću kontrolu nad alokacijom i dealokacijom memorije, ali također uvodi rizik od curenja memorije, visećih pokazivača i drugih grešaka povezanih s memorijom.
Kada razmotriti ručno upravljanje memorijom:
- Izuzetno osjetljiv kod na performanse: Ako je određeni dio vašeg koda izuzetno osjetljiv na performanse i GC pauze su neprihvatljive, ručno upravljanje memorijom može biti jedini način za postizanje potrebnih performansi.
- Determinističko upravljanje memorijom: Ako trebate preciznu kontrolu nad time kada se memorija alocira i dealocira, ručno upravljanje memorijom može pružiti potrebnu kontrolu.
- Okruženja s ograničenim resursima: U okruženjima s ograničenim resursima (npr. ugrađeni sustavi), ručno upravljanje memorijom može pomoći u smanjenju memorijskog otiska i poboljšanju ukupnih performansi sustava.
Kako implementirati ručno upravljanje memorijom:
- Linearna memorija: Koristite linearnu memoriju WebAssemblyja za ručnu alokaciju i dealokaciju memorije. Linearna memorija je neprekinuti blok memorije kojem WebAssembly kod može izravno pristupiti.
- Prilagođeni alokator: Implementirajte prilagođeni alokator memorije za upravljanje memorijom unutar prostora linearne memorije. To vam omogućuje kontrolu nad načinom alokacije i dealokacije memorije te optimizaciju za specifične obrasce alokacije.
- Pažljivo praćenje: Pažljivo pratite alociranu memoriju i osigurajte da se sva alocirana memorija na kraju dealocira. Neuspjeh u tome može dovesti do curenja memorije.
- Izbjegavajte viseće pokazivače: Osigurajte da se pokazivači na alociranu memoriju ne koriste nakon što je memorija dealocirana. Korištenje visećih pokazivača može dovesti do nedefiniranog ponašanja i rušenja.
Primjer: U aplikaciji za obradu zvuka u stvarnom vremenu, koristite ručno upravljanje memorijom za alokaciju i dealokaciju audio međuspremnika. To izbjegava GC pauze koje bi mogle prekinuti audio stream i dovesti do lošeg korisničkog iskustva. Implementirajte prilagođeni alokator koji pruža brzu i determinističku alokaciju i dealokaciju memorije. Koristite alat za praćenje memorije kako biste otkrili i spriječili curenje memorije.
Važna razmatranja: Ručnom upravljanju memorijom treba pristupiti s izuzetnim oprezom. Značajno povećava složenost vašeg koda i uvodi rizik od grešaka povezanih s memorijom. Razmislite o ručnom upravljanju memorijom samo ako imate temeljito razumijevanje principa upravljanja memorijom i spremni ste uložiti vrijeme i trud potrebne za njegovu ispravnu implementaciju.
Studije slučaja i primjeri
Kako bismo ilustrirali praktičnu primjenu ovih strategija optimizacije, pogledajmo neke studije slučaja i primjere.
Studija slučaja 1: Optimizacija WebAssembly game enginea
Game engine razvijen pomoću WebAssemblyja s GC-om imao je problema s performansama zbog čestih GC pauza. Profiliranje je otkrilo da je engine alocirao velik broj privremenih objekata svakog okvira, kao što su vektori, matrice i podaci o sudarima. Implementirane su sljedeće strategije optimizacije:
- Grupiranje objekata (Object Pooling): Implementirana su grupiranja objekata za često korištene objekte poput vektora, matrica i podataka o sudarima.
- Optimizacija struktura podataka: Korištene su učinkovitije strukture podataka za pohranu objekata igre i podataka o sceni.
- Smanjenje prijelaza između jezika: Prijenosi podataka između WebAssemblyja i JavaScripta minimizirani su grupiranjem podataka i korištenjem tipiziranih nizova.
Kao rezultat ovih optimizacija, vremena GC pauza su značajno smanjena, a broj sličica u sekundi (frame rate) game enginea se dramatično poboljšao.
Studija slučaja 2: Optimizacija WebAssembly knjižnice za obradu slika
Knjižnica za obradu slika razvijena pomoću WebAssemblyja s GC-om imala je problema s performansama zbog prekomjerne alokacije memorije tijekom operacija filtriranja slika. Profiliranje je otkrilo da je knjižnica stvarala nove međuspremnike slika za svaki korak filtriranja. Implementirane su sljedeće strategije optimizacije:
- Obrada slika na mjestu (In-Place): Operacije filtriranja slika modificirane su tako da rade na mjestu, mijenjajući izvorni međuspremnik slike umjesto stvaranja novih.
- Alokatori arena: Korišteni su alokatori arena za alokaciju privremenih međuspremnika za operacije obrade slika.
- Optimizacija struktura podataka: Korišteni su kompaktni prikazi podataka za pohranu slikovnih podataka, smanjujući memorijski otisak.
Kao rezultat ovih optimizacija, alokacija memorije je značajno smanjena, a performanse knjižnice za obradu slika su se dramatično poboljšale.
Najbolje prakse za podešavanje performansi WebAssembly GC-a
Pored strategija i tehnika o kojima smo raspravljali, evo nekih najboljih praksi za podešavanje performansi WebAssembly GC-a:
- Redovito profilirajte: Redovito profilirajte svoju aplikaciju kako biste identificirali potencijalna uska grla u performansama GC-a.
- Mjerite performanse: Mjerite performanse svoje aplikacije prije i nakon primjene strategija optimizacije kako biste osigurali da one zaista poboljšavaju performanse.
- Iterirajte i poboljšavajte: Optimizacija je iterativan proces. Eksperimentirajte s različitim strategijama optimizacije i poboljšavajte svoj pristup na temelju rezultata.
- Budite u toku: Budite u toku s najnovijim razvojem u području WebAssembly GC-a i performansi preglednika. Nove značajke i optimizacije se stalno dodaju u WebAssembly runtimeove i preglednike.
- Konzultirajte dokumentaciju: Konzultirajte dokumentaciju za vaš ciljni WebAssembly runtime i prevoditelj za specifične smjernice o optimizaciji GC-a.
- Testirajte na više platformi: Testirajte svoju aplikaciju na više platformi i preglednika kako biste osigurali da dobro radi u različitim okruženjima. Implementacije GC-a i karakteristike performansi mogu varirati između različitih runtimeova.
Zaključak
WebAssembly GC nudi moćan i praktičan način upravljanja memorijom u web aplikacijama. Razumijevanjem principa GC-a i primjenom strategija optimizacije o kojima smo raspravljali u ovom članku, možete postići izvrsne performanse i graditi složene, visokoperformantne WebAssembly aplikacije. Ne zaboravite redovito profilirati svoj kod, mjeriti performanse i iterirati na svojim strategijama optimizacije kako biste postigli najbolje moguće rezultate. Kako se WebAssembly nastavlja razvijati, pojavit će se novi GC algoritmi i tehnike optimizacije, stoga ostanite u toku s najnovijim razvojem kako biste osigurali da vaše aplikacije ostanu performantne i učinkovite. Prihvatite moć WebAssembly GC-a kako biste otključali nove mogućnosti u web razvoju i pružili izvanredna korisnička iskustva.